#include "WavFile.h"
#include "Common.h"
#include "shared_ptr.hpp"

#include <cstdlib>
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>

using namespace std;

WavFile::WavFile(const byte* wav, uint32 wavLen, BYTE_ORDER_T byteOrder)
    : _isValidWavFile(true), _data(NULL), _dataLen(0), _byteOrder(byteOrder)
{
    if (wav == NULL || wavLen <= WAV_HEADER_LEN)
    {
        _isValidWavFile = false;
        return;
    }

    _chunkID = (wav[0] << 24) | (wav[1] << 16) | (wav[2] << 8) | wav[3];
    if (_chunkID != WAV_ID_RIFF)
    {
        _isValidWavFile = false;
        return;
    }

    // little-endian
    _chunkSize = wav[4] | (wav[5] << 8) | (wav[6] << 16) | (wav[7] << 24);
    if (_chunkSize + 8 != wavLen)
    {
        _isValidWavFile = false;
        return;
    }

    _format = (wav[8] << 24) | (wav[9] << 16) | (wav[10] << 8) | wav[11];
    if (_format != WAV_ID_WAVE)
    {
        _isValidWavFile = false;
        return;
    }

    _subChunk1ID = (wav[12] << 24) | (wav[13] << 16) | (wav[14] << 8) | wav[15];
    if (_subChunk1ID != WAV_ID_FMT)
    {
        _isValidWavFile = false;
        return;
    }

    // little-endian
    _subChunk1Size = wav[16] | (wav[17] << 8) | (wav[18] << 16) | (wav[19] << 24);
    if (_subChunk1Size != 0x10)
    {
        _isValidWavFile = false;
        return;
    }

    // little-endian
    _audioFormat = wav[20] | (wav[21] << 8);
    if (_audioFormat != 0x01)
    {
        _isValidWavFile = false;
        return;
    }

    // little-endian
    _numChannels = wav[22] | (wav[23] << 8);
    if (_numChannels != 0x01 && _numChannels != 0x02)
    {
        _isValidWavFile = false;
        return;
    }

    // little-endian
    _sampleRate = wav[24] | (wav[25] << 8) | (wav[26] << 16) | (wav[27] << 24);
    _byteRate = wav[28] | (wav[29] << 8) | (wav[30] << 16) | (wav[31] << 24);
    _blockAlign = wav[32] | (wav[33] << 8);
    _bitsPerSample = wav[34] | (wav[35] << 8);
    if ((_blockAlign != _numChannels * _bitsPerSample / 8)
        || _byteRate != _sampleRate * _numChannels * _bitsPerSample / 8
        || (_bitsPerSample != 16 && _bitsPerSample != 8))
    {
        _isValidWavFile = false;
        return;
    }

    _subChunk2ID = (wav[36] << 24) | (wav[37] << 16) | (wav[38] << 8) | wav[39];
    if (_subChunk2ID != WAV_ID_DATA)
    {
        _isValidWavFile = false;
        return;
    }
    _subChunk2Size = wav[40] | (wav[41] << 8) | (wav[42] << 16) | (wav[43] << 24);
    if (_subChunk2Size + WAV_HEADER_LEN != wavLen)
    {
        _isValidWavFile = false;
        return;
    }

    _dataLen = _subChunk2Size;
    _data = new byte[_dataLen];
    memcpy(_data, wav + 44, _dataLen);
}

WavFile::~WavFile()
{
    delete [] _data;
}

WavFile* WavFile::Create(const byte* wav, uint32 wavLen, BYTE_ORDER_T byteOrder)
{
    WavFile* wavFile = new WavFile(wav, wavLen, byteOrder);
    if (!wavFile->IsValidWavFile())
    {
        delete wavFile;
        wavFile = NULL;
    }

    return wavFile;;
}

void WavFile::Print()
{
    printf("ChunkID=[%u], ChunkSize=[%u], Format=[%u]\n"
            "SubChunk1ID=[%u], SubChunk1Size=[%u], AudioFormat=[%u], NumChannels=[%u], SampleRate=[%u], ByteRate=[%u], BlockAlign=[%u], BitsPerSample=[%u]\n"
            "SubChunk2ID=[%u], SubChunk2Size=[%u]\n"
            "IsValidWavFile=[%u], ByteOrder=[%u]\n",
            _chunkID, _chunkSize, _format,
            _subChunk1ID, _subChunk1Size, _audioFormat, _numChannels, _sampleRate, _byteRate, _blockAlign, _bitsPerSample,
            _subChunk2ID, _subChunk2Size,
            _isValidWavFile, _byteOrder);
}

WavFile* WavFile::CreateTestWavFile(string inPath)
{
    if (inPath.size() == 0)
    {
        return NULL;
    }

    struct stat fileInfo;
    stat(inPath.c_str(), &fileInfo);
    if (fileInfo.st_size < 0)
    {
        printf("stat %s wrong\n", inPath.c_str());
        return NULL;
    }
    printf("%s size = [%u]\n", inPath.c_str(), fileInfo.st_size);

    int fd = open(inPath.c_str(), O_RDONLY);
    if (fd < 0)
    {
        printf("Failed to open %s\n", inPath.c_str());
        return NULL;
    }

    boost::shared_ptr<byte> fbuf(new byte[fileInfo.st_size]);
    int n = readn(fd, fbuf.get(), fileInfo.st_size);
    if (n != fileInfo.st_size)
    {
        printf("Failed to read file %s\n", inPath.size());
        return NULL;
    }
    close(fd);

    WavFile* wav = WavFile::Create(fbuf.get(), fileInfo.st_size, ENDIAN_BIG);
    return wav;
}

int WavFile::GetWavSamples(lame_t gfp, int* leftTone, int* rightTone, int lenOfTone)
{
    if (leftTone == NULL || rightTone == NULL || lenOfTone <= 0)
    {
        return -1;
    }

    uint16 num_channels = _numChannels;
    uint32 numSamples = GetNumSamples();

    boost::shared_ptr<int> pcmIntBuf(new int[numSamples * 2]);
    int bytesPerSample = _bitsPerSample / 8;
    int samples_read = UnpackSamples(_byteOrder, pcmIntBuf.get(), numSamples * 2);

    if (samples_read < 0)
    {
        return samples_read;
    }

    int* p = pcmIntBuf.get() + samples_read;
    samples_read /= num_channels;

    /* output to int buffer */
    if (num_channels == 2)
    {
        for (int i = samples_read - 1; i >= 0; i--)
        {
            rightTone[i] = *--p;
            leftTone[i] = *--p;
        }

        return samples_read;
    }
    if (num_channels == 1)
    {
        for (int i = samples_read - 1; i >= 0; i--)
        {
            leftTone[i] = *--p;
        }

        return samples_read;
    }

    printf("Error: num-channels should be 1 / 2\n");
    return -2;
}

int WavFile::UnpackSamples(BYTE_ORDER_T byteOrder, int* pcmSampleIntBuf, int bufSize)
{
    int32 samplesToRead = GetNumSamples() * _numChannels;
    if (pcmSampleIntBuf == NULL || bufSize <= 0 || bufSize < samplesToRead)
    {
        return -1;
    }

    int32 bytesPerSample = _bitsPerSample / 8;

    int32* op = pcmSampleIntBuf + samplesToRead;

    const int32 b = sizeof(int) * 8;
    for (int32 i = _dataLen - bytesPerSample; i >= 0; i -= bytesPerSample)
    {
        byte *ip = _data; /* input pointer */
        * --op = byteOrder == ENDIAN_LITTLE
            ? ip[i] << (b - 16) | ip[i + 1] << (b - 8)
            : ip[i] << (b - 8) | ip[i + 1] << (b - 16);
    }

    return (samplesToRead);
}

int WavFile::ToMP3(boost::shared_ptr<Item>& item)
{
    lame_global_flags* gf = lame_init();
    item.get()->_dataLen = 0;
    if (gf == NULL)
    {
        free(gf);
        printf("lame_init() failed \n");
        return -1;
    }

    InitLameFlags(gf);
    if (lame_init_params(gf) < 0)
    {
        free(gf);
        printf("Failed to init params\n");
        return -1;
    }

    int numSamples = GetNumSamples();
    int mp3bufSize = numSamples * 2 + 7200;
    boost::shared_ptr<byte> mp3buf(new byte[mp3bufSize]);

    boost::shared_ptr<int> leftTone(new int[numSamples]);
    boost::shared_ptr<int> rightTone(new int[numSamples]);

    int iread = GetWavSamples(gf, leftTone.get(), rightTone.get(), numSamples);
    printf("Get_audio: [%d]\n", iread);

    if (iread <= 0)
    {
        free(gf);
        return -1;
    }

    int mp3EncBufSize = lame_encode_buffer_int(gf, leftTone.get(), rightTone.get(), iread,
        mp3buf.get(), mp3bufSize);
    if (mp3EncBufSize < 0 || mp3EncBufSize > MAX_BUF_LEN)
    {
        free(gf);
        return -1;
    }

    memcpy(item.get()->_data + item.get()->_dataLen, mp3buf.get(), mp3EncBufSize);
    item.get()->_dataLen += mp3EncBufSize;

    int mp3EncFlushSize = lame_encode_flush(gf, mp3buf.get(), mp3bufSize);
    if (mp3EncFlushSize < 0 || mp3EncFlushSize + mp3EncBufSize > MAX_BUF_LEN)
    {
        free(gf);
        return -1;
    }

    memcpy(item.get()->_data + item.get()->_dataLen, mp3buf.get(), mp3EncFlushSize);
    item.get()->_dataLen += mp3EncFlushSize;

    return 0;
}

void WavFile::InitLameFlags(lame_global_flags* gf)
{
    lame_set_num_channels(gf, _numChannels);
    lame_set_in_samplerate(gf, _sampleRate);
    lame_set_num_samples(gf, GetNumSamples());
}
